package controllers

import (
	"fmt"
	"ginvueadmin/global"
	"ginvueadmin/middleware"
	"ginvueadmin/models"
	"ginvueadmin/models/request"
	"ginvueadmin/models/response"
	"ginvueadmin/service"
	"ginvueadmin/utils"
	"time"

	"github.com/dgrijalva/jwt-go"
	"github.com/gin-gonic/gin"
	"github.com/go-redis/redis"
)

func Index(c *gin.Context) {
	c.JSON(200, gin.H{
		"code": 200,
		"msg":  "Hello World",
	})
}

// @Summary 用户登录
// @Produce  application/json
// @Param data body request.RegisterAndLoginStruct true "用户登录接口"
// @Success 200 {string} string "{"success":true,"data":{},"msg":"登陆成功"}"
// @Router post /base/login
func Login(c *gin.Context) {
	/*
		1. 获取参数 username，password，captcha，captchaId
		2. 校验获取到的参数
		3. 判断验证码
		4. 调用模型中的 Login 方法，是否入库成功
			a. Login 调用成功，则发放 token
	*/

	var L request.RegisterAndLoginStruct
	c.ShouldBindJSON(&L)

	userVerify := utils.Rules{
		"UserName":  {utils.NotEmpty()},
		"Password":  {utils.NotEmpty()},
		"Captcha":   {utils.NotEmpty()},
		"CaptchaId": {utils.NotEmpty()},
	}

	userVerifyErr := utils.Verify(L, userVerify)
	if userVerifyErr != nil {
		global.FailWithMessage(userVerifyErr.Error(), c)
		return
	}

	if store.Verify(L.CaptchaId, L.Captcha, true) {
		u := &models.SysUser{Username: L.UserName, Password: L.Password}
		if user, err := service.Login(u); err != nil {
			global.FailWithMessage(fmt.Sprintf("用户名密码错误或 %v", err), c)
		} else {
			tokenNext(c, *user)
		}
	} else {
		global.FailWithMessage("验证码错误", c)
	}
}

// 生成用户认证令牌
//
// @param {pointer} c gin 的上下文
// @param {struct} user user struct
func tokenNext(c *gin.Context, user models.SysUser) {
	/*
		1. 首先，参数是 user 的 SysUser 结构体。
		2. 接着，创建一个 JWT 对象，并使用全局配置中的签名密钥作为参数初始化当前 JWT 对象。
		3. 然后，创建了一个包含用户信息以及一些标准声明的 clams 结构体，其中包括用户的 UUID、ID、昵称、用户名、权限 ID 等信息。此外还设置了缓冲时间、生效时间、过期时间和签发者等标准声明内容。
		4. 调用 JWT 对象的 CreateToken 方法，利用之前创建的 clams 结构体生成一个用户令牌 token，并检查是否有错误发生。
			a. 若错误，则提示获取 token 失败，并 return 结束该函数
			b. 若正常生成 token，则执行后续逻辑
		5. 判断全局配置中是否为多点登录
			a. 未使用多点登录（useMultipoint=false），则返回一个成功的响应，包含用户信息、token 和过期时间。
			b. 系统使用多点登录（useMultipoint=true），则首先尝试从 Redis 中获取之前存储的用户令牌。
				i. 若获取失败（err == redis.Nil），则将新的 token 存储到 Redis 中，并返回成功的响应
				ii. 若获取成功，将原 token 加入到 JWT 黑名单中，并更新 Redis 中的令牌信息，然后返回成功的响应。
	*/

	// 创建唯一签名
	j := &middleware.JWT{
		SigningKey: []byte(global.CONFIG.Jwt.SigningKey),
	}

	claims := &request.CustomClaims{
		UUID:        user.UUID,
		ID:          user.ID,
		NickName:    user.NickName,
		Username:    user.Username,
		AuthorityId: user.AuthorityId,
		BufferTime:  60 * 60 * 24, // 缓冲时间 1 天，缓冲时间内会获得新的 token 刷新令牌，此时一个用户会存在两个有效令牌，但是前端只留一个，另一个会丢失
		StandardClaims: jwt.StandardClaims{
			NotBefore: time.Now().Unix() - 1000,       // 签名生效时间
			ExpiresAt: time.Now().Unix() + 60*60*24*7, // 过期时间 7 天
			Issuer:    "qm",                           // 签名发行者
		},
	}

	token, err := j.CreateToken(*claims)
	if err != nil {
		global.FailWithMessage("获取 token 失败", c)
		return
	}

	// 单点登录
	if !global.CONFIG.System.UseMultipoint {
		global.OkWithData(response.LoginResponse{
			User:      user,
			Token:     token,
			ExpiresAt: claims.StandardClaims.ExpiresAt * 1000,
		}, c)
		return
	}

	// 多点登录

	jwtStr, err := service.GetRedisJwt(user.Username)
	if err == redis.Nil {
		// 将 token 更新到 redis 中
		if err := service.SetRedisJwt(token, user.Username); err != nil {
			global.FailWithMessage(fmt.Sprintf("设置 token，更新 redis 失败，%v", err), c)
			return
		}

		// 返回成功响应
		global.OkWithData(response.LoginResponse{
			User:      user,
			Token:     token,
			ExpiresAt: claims.StandardClaims.ExpiresAt * 1000,
		}, c)
	} else if err != nil {
		global.FailWithMessage(fmt.Sprintf("%v", err), c)
	} else {
		// 将 token 加入到 jwt 黑名单中，并更新 redis 中 token 信息，然后返回成功响应
		var blackJwt models.JwtBlacklist
		blackJwt.Jwt = jwtStr

		if err := service.JsonInBlacklist(blackJwt); err != nil {
			global.FailWithMessage(fmt.Sprintf("jwt 作废失败，%v", err), c)
			return
		}

		if err := service.SetRedisJwt(token, user.Username); err != nil {
			global.FailWithMessage(fmt.Sprintf("设置 token，更新 redis 失败，%v", err), c)
			return
		}

		global.OkWithData(response.LoginResponse{
			User:      user,
			Token:     token,
			ExpiresAt: claims.StandardClaims.ExpiresAt * 1000,
		}, c)
	}
}

// @Summary 用户注册账号
// @Produce  application/json
// @Param data body request.RegisterAndLoginStruct true "用户注册接口"
// @Success 200 {string} string "{"success":true,"data":{},"msg":"登陆成功"}"
// @Router post /base/register
func Register(c *gin.Context) {
	/*
		1. func Register(c *gin.Context)：这是一个名为 Register 的函数，接受一个 gin.Context 类型的参数 c，用于处理 HTTP 请求和响应。
		2. var R request.RegisterStruct：定义了一个名为 R 的 RegisterStruct 结构体变量，用于存储从 HTTP 请求中解析得到的注册信息。
		3. _ = c.ShouldBindJSON(&R)：通过 gin 框架的 ShouldBindJSON 方法，将 HTTP 请求中的 JSON 数据绑定到结构体变量 R 中。
		4. UserVerify := utils.Rules{...}：定义了一个 UserVerify 变量，使用了 utils 包中的 Rules 结构体，该结构体包含了对用户注册信息的验证规则，如用户名、昵称、密码、权限ID 的非空验证规则。
		5. UserVerifyErr := utils.Verify(R, UserVerify)：调用 utils 包中的 Verify 方法，对注册信息进行验证，如果验证失败，则返回相应的错误信息。
		6. 如果注册信息验证失败，会通过 global.FailWithMessage 返回错误信息，并结束函数执行；否则，继续执行后续逻辑。
		7. 创建一个 SysUser 结构体 user，并将从 HTTP 请求中获取的注册信息赋值给该结构体变量。
		8. err, userReturn := service.Register(*user)：调用 service 包中的 Register 方法，将用户注册信息传入，尝试进行注册操作，并获取返回的错误信息和注册成功的用户信息。
		9. 根据注册操作的结果，如果发生错误，则通过 global.FailWithDetailed 返回详细的错误信息和注册失败的原因；如果注册成功，则通过 global.OkDetailed 返回注册成功的信息和注册成功的用户信息。
	*/

	/*
		1. 定义 R 的 RegisterStruct 结构体变量，用于存储从 HTTP 请求中解析得到的注册信息。
		2. 通过 gin 框架的 ShouldBindJSON 方法，将 HTTP 请求中的 JSON 数据绑定到结构体变量 R 中。
		3. 定义 UserVerify 变量，使用了 utils 包中的 Rules 结构体，该结构体包含了对用户注册信息的验证规则，如用户名、昵称、密码、权限ID 的非空验证规则。
		4. 调用 utils 包中的 Verify 方法，对注册信息进行验证，如果验证失败，则返回相应的错误信息。
			a. 如果注册信息验证失败，会通过 global.FailWithMessage 返回错误信息，并结束函数执行
			b. 否则，继续执行后续逻辑。
		5. 创建一个 SysUser 结构体 user，并将从 HTTP 请求中获取的注册信息赋值给该结构体变量。
		6. 调用 service 包中的 Register 方法，将用户注册信息传入，尝试进行注册操作，并获取返回的错误信息和注册成功的用户信息。根据注册操作结果：
			a. 如果发生错误，则通过 global.FailWithDetailed 返回详细的错误信息和注册失败的原因；
			b. 如果注册成功，则通过 global.OkDetailed 返回注册成功的信息和注册成功的用户信息。
	*/

	var R request.ResiterStruct
	c.ShouldBindJSON(&R)
	userVerify := utils.Rules{
		"Username": {utils.NotEmpty()},
		"Password": {utils.NotEmpty()},
		// TODO... 先注释
		// "NickName":    {utils.NotEmpty()},
		// "AuthorityId": {utils.NotEmpty()},
	}

	if userVerifyErr := utils.Verify(R, userVerify); userVerifyErr != nil {
		global.FailWithMessage(userVerifyErr.Error(), c)
		return
	}

	user := &models.SysUser{Username: R.Username, Password: R.Password, NickName: R.NickName, HeaderImage: R.HeaderImage, AuthorityId: R.AuthorityId}

	if userInfo, err := service.Register(*user); err != nil {
		global.FailWithDetailed(global.ERROR, response.SysUserReponse{User: userInfo}, fmt.Sprintf("%v", err), c)
	} else {
		global.OkDetailed(response.SysUserReponse{User: userInfo}, "注册成功", c)
	}
}
